Skip to content

get() ​

本节目标:理解“get()”的核心思路,并能把它用于实际开发或面试表达。

作用:用于拦截某个属性的读取操作
参数:可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。
var person = {
    name: "张三"
};
var proxy = new Proxy(person, {
    get: function (target, propKey) {
        if (propKey in target) {
            return target[propKey];
        } else {
            throw new ReferenceError("Prop name \"" + propKey + "\" does not exist.");
        }
    }
});
proxy.name // "张三"
proxy.age // 抛出一个错误
上面代码表示,如果访问目标对象不存在的属性,会抛出一个错误。如果没有这个拦截函数,访问不存在的属性,只会返回undefined。
get方法可以继承。
let proto = new Proxy({}, {
    get(target, propertyKey, receiver) {
        console.log('GET ' + propertyKey);
        return target[propertyKey];
    }
});
let obj = Object.create(proto);
obj.foo // "GET foo"
上面代码中,拦截操作定义在Prototype对象上面,所以如果读取obj对象继承的属性时,拦截会生效。
**应用举例**
下面的例子使用get拦截,实现数组读取负数的索引。
function createArray(...elements) {
    let handler = {
        get(target, propKey, receiver) {
            let index = Number(propKey);
            if (index < 0) {
                propKey = String(target.length + index);
            }
            return Reflect.get(target, propKey, receiver);
        }
    };
    let target = [];
    target.push(...elements);
    return new Proxy(target, handler);
}
let arr = createArray('a', 'b', 'c');
arr[-1] // c
上面代码中,数组的位置参数是-1,就会输出数组的倒数第一个成员。
可以将读取属性的操作(get),转变为执行某个函数,从而实现属性的链式操作。
var pipe = function (value) {
    var funcStack = [];
    var oproxy = new Proxy({}, {
        get: function (pipeObject, fnName) {
            if (fnName === 'get') {
                return funcStack.reduce(function (val, fn) {
                    return fn(val);
                }, value);
            }
            funcStack.push(window[fnName]);
            return oproxy;
        }
    });
    return oproxy;
}
var double = n => n * 2;
var pow = n => n * n;
var reverseInt = n => n.toString().split("").reverse().join("") | 0;
pipe(3).double.pow.reverseInt.get; // 63
上面代码设置 Proxy 以后,达到了将函数名链式使用的效果。
下面的例子则是利用get拦截,实现一个生成各种 DOM 节点的通用函数dom。
const dom = new Proxy({}, {
    get(target, property) {
        return function (attrs = {}, ...children) {
            const el = document.createElement(property);
            for (let prop of Object.keys(attrs)) {
                el.setAttribute(prop, attrs[prop]);
            }
            for (let child of children) {
                if (typeof child === 'string') {
                    child = document.createTextNode(child);
                }
                el.appendChild(child);
            }
            return el;
        }
    }
});
const el = dom.div({},
    'Hello, my name is ',
    dom.a({ href: '//example.com' }, 'Mark'),
    '. I like:',
    dom.ul({},
        dom.li({}, 'The web'),
        dom.li({}, 'Food'),
        dom.li({}, '…actually that\'s it')
    )
);
document.body.appendChild(el);
下面是一个get方法的第三个参数的例子,它总是指向原始的读操作所在的那个对象,一般情况下就是 Proxy 实例。
const proxy = new Proxy({}, {  get: function(target, key, receiver) {    return receiver;  }});proxy.getReceiver === proxy // true
上面代码中,proxy对象的getReceiver属性是由proxy对象提供的,所以receiver指向proxy对象。
const proxy = new Proxy({}, {  get: function(target, key, receiver) {    return receiver;  }});
const d = Object.create(proxy);d.a === d // true
上面代码中,d对象本身没有a属性,所以读取d.a的时候,会去d的原型proxy对象找。这时,receiver就指向d,代表原始的读操作所在的那个对象。
如果一个属性不可配置(configurable)且不可写(writable),则 Proxy 不能修改该属性,否则通过 Proxy 对象访问该属性会报错。
const target = Object.defineProperties({}, {  foo: {    value: 123,    writable: false,    configurable: false  },});
const handler = {  get(target, propKey) {    return 'abc';  }};
const proxy = new Proxy(target, handler);
proxy.foo// TypeError: Invariant check failed

本站总访问 — 次